import java.util.*;

import com.thoughtworks.xstream.XStream;

class ROI
{
	// Original image file name
	private String filename;

	// Upper left corner of ROI in original image
	private int original_xpos;
	private int original_ypos;

	// ROI width, height
	private int width;
	private int height;

	private ImageData original_image;
	private ImageData binary_image;
	private ImageData labeled_image;

	private Vector<Mass> Mass_Vector;

	////////////////////
	// Methods
	

	/**
	 * Load image from file.
	 * ROI is full image.
	 */
	public ROI(String filename)
	{
		original_image = new ImageData(filename);
		original_xpos = 0;
		original_ypos = 0;
		width = original_image.getWidth();
		height = original_image.getHeight();
		binary_image = null;
		labeled_image = null;

		Mass_Vector = new Vector<Mass>();
		test_init();
	}

	private void test_init()
	{
		for(int i = 1; i<=2; i++) {
			Mass_Vector.add(new Mass(this, i));
		}
		binary_image = original_image.threshold(127);
		labeled_image = massDetect(binary_image);
		findMasses();
	}	
	
	/**
	 * Load image from another image.
	 * ROI is given by x, y, w, h
	 */
	public ROI(ImageData img, int x, int y, int w, int h)
	{
		original_image = new ImageData(img, x, y, w, h);
		original_xpos = x;
		original_ypos = y;
		width = w;
		height = h;

		binary_image = null;
		labeled_image = null;
		Mass_Vector = new Vector<Mass>();
		
		test_init();
	}

	public ImageData getOriginalImage()	{ return original_image; }
	public ImageData getBinaryImage()		{ return binary_image; }
	public ImageData getLabeledImage()	{ return labeled_image; }

	public String toXML()
	{
		XStream xstream = new XStream();
		return xstream.toXML(this);
	}
	
	/**
	 * Adds a mass object to Mass_Vector
	 */
	public boolean addMass(Mass arg0) {
		return Mass_Vector.add(arg0);
	}

	/**
	 * Returns list of Mass_Vector Mass objects
	 */
	public Vector<Mass> getMasses()
	{
		return Mass_Vector;
	}

	/**
	 * Method that Ningyi will create
	 */
	public void preprocess()
	{

	}
	
	/**
	 * Method Gurpreet/Eddie to create that does mass detection.
	 * Could have versions where pass parameters like massDetect(threshold,???)
	 * @param binary_image with colors set to either 0 or 255.
	 * @return ImageData with pixels/colors set to labels 0(no mass), 1(first mass), ..., k. 
	 */
	public ImageData massDetect(ImageData binary_image)
	{
		int[] pixels = binary_image.allocPixels();
		binary_image.getPixels(pixels, 0, 0, width, height);

		// TODO Properly label image.
		ArrayDeque<Integer> pixel_indices = new ArrayDeque<Integer>();		
		for(int i=0; i<pixels.length; i++) {
			pixel_indices.add(i);
		}
		
		int label_counter = 0;
		while(!pixel_indices.isEmpty()) {
			int current_pixel_index = pixel_indices.removeFirst();
			if(Util.getRGBavg(pixels[current_pixel_index]) == 255) {
				label_counter++;
				labelPixel(pixels, current_pixel_index, label_counter);
				pixel_indices.remove(current_pixel_index);
				ArrayDeque<Integer> index_of_neighbors = new ArrayDeque<Integer>();
				addRasterNeighbors(current_pixel_index, index_of_neighbors, width, height);

				while(!index_of_neighbors.isEmpty()){
					int neighbor_pixel_index = index_of_neighbors.removeFirst();
					if(Util.getRGBavg(pixels[neighbor_pixel_index]) == 255) {
						labelPixel(pixels, neighbor_pixel_index, label_counter);
						pixel_indices.remove(neighbor_pixel_index);
						addRasterNeighbors(neighbor_pixel_index, index_of_neighbors, width, height);
					}
				}
			}
		}
//		tempLabelImage(pixels);
		
		ImageData result = binary_image.clone();
		result.setPixels(pixels, 0, 0,
				width, height);
		return result;
	}

	private void labelPixel(int[] pixels, int current_pixel_index, int label_counter) {
		pixels[current_pixel_index] = Util.setRGB(label_counter, label_counter, label_counter);
	}

	public static void addRasterNeighbors(int pixel_index, ArrayDeque<Integer> index_of_neighbors, int width, int height) {
		int y = pixel_index / width;
		int x = pixel_index % width;
		// 000
		// 01x
		// 000
		if(x < width - 1){
			index_of_neighbors.add(y*width + x + 1);
		}
		if(y < height -1) {
			// 000
			// 010
			// x00
			if(x > 0) {
				index_of_neighbors.add((y+1)*width + x - 1);						
			}
			// 000
			// 010
			// 0x0
			index_of_neighbors.add((y+1)*width + x);
			// 000
			// 010
			// 00x
			if(x < width - 1) {
				index_of_neighbors.add((y+1)*width + x + 1);							
			}
		}
	}
	
	/**
	 * For testing/devel use only.
	 * Currently, only label first 3 rows with labels 1, 2, and 3.
	 * @see {@link ROI#massDetect(ImageData)} 
	 * @param pixels binary image containing 0 and 255
	 */
	private void tempLabelImage(int[] pixels) {
		for(int y = 0; y < height; y++) {
			for(int x = 0; x < width; x++) {
				pixels[y*width + x] = Util.setRGB(0, 0, 0);
			}
		}
		for(int y = 0; y < 3; y++){
			tempLabelRow(y, y+1, pixels);	
		}
	}

	/**
	 * For testing/devel use only.
	 * @param row
	 * @param label
	 * @param pixels
	 */
	private void tempLabelRow(int row, int label, int[] pixels) {
		for(int x = 0; x < width; x++) {
			pixels[row*width + x] = Util.setRGB(label, label, label);
		}
	}
	
	/**
	 * Scans current binary_image to create a labeled image from it ...
	 * detecting binary blobs.
	 */

	/**
	 * Method that Boni will create to detect blobs from labeled_image.
	 * The value of the label is taken to be the average of the rgb
	 * component pixel values.  For example, a pixel with an rgb
	 * value of (32, 32, 32) results in the label 32.
	 */
	public void findMasses()
	{
		// We'll use a Map to link the found masses with our
		// newly created Mass objects.
		HashMap<Integer, Mass> foundMasses = new HashMap<Integer, Mass>();

		// Reset our Mass_Vector
		Mass_Vector.clear(); // Boni: Should we do this here???

		// Get pixels
		int[] pixels = labeled_image.allocPixels();
		labeled_image.getPixels(pixels, 0, 0, width, height);

		// Add each found label to founMasses vector
		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{
				int pixel = pixels[y*width + x];
				int label = Util.getRGBavg(pixel);
				if (label > 0)
				{
					Mass mass;

					// Add if this is a new label,
					// otherwise retrieve the Mass object.
					if (!foundMasses.containsKey(label))
					{
						p.print("Found new label: " + label);
						mass = new Mass(this, label);
						foundMasses.put(label, mass);
					}
					else
					{
						mass = foundMasses.get(label);
					}

					// Update the mass bounding box
					mass.updateBoundingBox(x, y);
				}
			}
		}

		// Save pointer to each created Mass object
		p.print("# of found labels: " + foundMasses.size());
		Iterator<Mass> it = foundMasses.values().iterator();
		while (it.hasNext())
		{
			Mass mass = it.next();
			this.addMass(mass);
			//mass.dump_BoundingBox();	// debug
		}
	}

	public String getFilename() {
		return filename;
	}

	public int getHeight() {
		return height;
	}

	public int getOriginal_xpos() {
		return original_xpos;
	}

	public int getOriginal_ypos() {
		return original_ypos;
	}

	public int getWidth() {
		return width;
	}

	public void setBinaryImage(ImageData binary_image) {
		this.binary_image = binary_image;
	}

	public void setLabeledImage(ImageData labeled_image) {
		this.labeled_image = labeled_image;
	}
}
